/**
 * Copyright (c) 2009, Nathan Bubna
 * Dual licensed under the MIU and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gno.org/licenses/gpl.html
 *
 * Provides ability to delay an action for a short time, while
 * offering the user the option to "undo" (cancel) the action.
 * Also watches for page unload to ensure the the action is
 * executed before the page goes away.
 *
 * $.undo('You deleted X', fnToDeleteX);
 *
 * @version 1.0
 * @requires $.measure
 * @supports $.pulse
 * @supports $.txt
 * @author Nathan Bubna
 */
;(function ($) {

    $.undo = function(txtOrAct, opts) {
        return $('body').undo(txtOrAct, opts);
    };
    var U = $.fn.undo = function(txtOrAct, opts) {
        opts = U.toOpts(txtOrAct, opts);
        U.plugin.call(this, $.undo, opts, function(o) {
            if (o.finish === true) {
                o = this.data('undo');
                if (o) o._finish();
            } else if (o.revert === true) {
                o = this.data('undo');
                if (o) o._revert();
            } else {
                U.setup.call(this, o);
            }
        });
        return this;
    },
    $W = $(window);

    // all that's configurable...
    $.extend(true, $.undo, $.place, {
        version: "1.0",
        text: null,
        finish: null,
        revert: null,
        time: 10*1000,
        fade: 1.5*1000,
        align: 'top-center',
        undoClass: 'undo',
        element: '<a class="undo-revert">Undo</a>'+
                 '<a class="undo-finish">X</a>',
        revertButton: '.undo-revert',
        finishButton: '.undo-finish'
    });

    // all that's extensible...
    $.extend(true, $.fn.undo, $.fn.place, {
        setup: function(o) {
            o.parent = this.data('undo', o).place(o);
            o.place = this.data('place');
            var el = o.self = o.place.self.addClass(o.undoClass);

            o._finish = U.decision(o, o.finish);
            o.id = setTimeout(o._finish, o.time);
            if (o.finish) $W.bind('unload', o._finish);
            el.find(o.finishButton).click(o._finish);

            o._revert = U.decision(o, o.revert);
            el.find(o.revertButton).click(o._revert);
        },
        teardown: function(o) {
            this.data('undo', null);
            o.self.remove();
            clearTimeout(o.id);
            if (o.finish) $W.unbind('unload', o._finish);
        },
        decision: function(o, fn) {
            return function() {
                U.teardown.call(o.parent, o);
                if (fn) fn.call(o.self, o);
            };
        },
        toOpts: function(t, o) {
            // if first arg boolean, we can ignore the rest
            if (typeof t == "boolean") return t ? { revert: true } : { finish: true };
            if ($.isPlainObject(t)) return t;
            // t must be txt string
            if (!o) o = {};
            o.text = t;
            return o;
        }
    });

})(jQuery);
